# Copyright (c) 2018 Presto Labs Pte. Ltd.
# Author: wooyaggo

import json
import os
import re
import time

import requests

from urllib.parse import urlencode

import coin.exchange.base.kr_rest.rest_client_base as rcb
from coin.exchange.util.order_checker import OrderAmountChecker
from coin.exchange.base.kr_rest.auth_base import AuthKey
from coin.exchange.util.rate_limit import RateLimiter
import coin.exchange.bithumb.kr_rest_web.constants as exchange_constants


class BithumbOrderBase(object):
  def __init__(self, cookie_file):
    assert os.path.exists(cookie_file), cookie_file

    cookies = json.load(open(cookie_file))

    self.session = requests.Session()
    for c in cookies:
      self.session.cookies.set(c['name'], c['value'], domain=c['domain'])

    headers = self.session.headers
    headers['Origin'] = 'https://www.bithumb.com'
    headers['Accept-Encoding'] = 'gzip, deflate, br'
    headers['Accept-Language'] = 'en-GB,en;q=0.9,en-US;q=0.8,ko;q=0.7'
    headers['Accept'] = 'application/json, text/javascript, */*; q=0.01'
    headers['Authority'] = 'www.bithumb.com'
    headers['X-Requested-With'] = 'XMLHttpRequest'
    # User-Agent should be same in the one for login procedure.
    headers[
        'User-Agent'] = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.119 Safari/537.36'

    # TODO(wooyaggo): What is Ratelimiter? Do I have to use it.
    self.rate_limit = RateLimiter(5, 1.0)

  def _query_nocheck(self, endpoint, request_type, params=None, headers=None):
    assert request_type in ['GET', 'POST']

    req_timestamp = time.time()
    if request_type == 'GET':
      url = endpoint + '?' + urlencode(params)
      resp = self.session.get(url)
    elif request_type == 'POST':
      resp = self.session.post(endpoint, params, headers=headers)
    else:
      raise NotImplementedError('Not supported request_type: ' + request_type)
    res_timestamp = time.time()

    try:
      content = json.loads(resp.text)
    except Exception:
      raise Exception('Invalid format: ' + repr(resp.text))

    return rcb.RestUpdate(req_timestamp, res_timestamp, content, native_header=resp.headers)

  def _query(self, endpoint, request_type, params=None, headers=None, disable_rate_limit=False):
    if not disable_rate_limit:
      while not self.rate_limit.check():
        time.sleep(0.1)
    else:
      self.rate_limit.check()
    return self._query_nocheck(endpoint, request_type, params=params, headers=headers)


class BithumbWebNativePrivateClient(BithumbOrderBase):
  def __init__(self, cookie_file):
    BithumbOrderBase.__init__(self, cookie_file)
    self.order_amount_checker = OrderAmountChecker("KRW", 500, 50000000)

  @staticmethod
  def _assert_success(content):
    assert content.msg['error'] == exchange_constants.RETURN_CODE_SUCCESS, \
      content

  def _send_order(self, currency, price, qty, order_side):
    assert currency in exchange_constants.coin_list, \
      'Not supported currency: ' + currency
    if type(price) == str and price.isdigit():
      price = int(price)
    assert type(price) == int and price > 0
    if type(qty) == str and re.match("^\d+(\.\d+){0,1}$", qty):
      qty = float(qty)
    if type(qty) == int:
      qty = float(qty)
    assert type(qty) == float and qty > 0
    assert order_side in ["buy", "sell"]

    self.order_amount_checker.check_amount("KRW", price, qty)

    # Sample XRP request URL: https://www.bithumb.com/trade/execBuy/XRP
    if order_side == 'buy':
      query_method = 'execBuy'
    elif order_side == 'sell':
      query_method = 'execSell'
    else:
      raise Exception('Invalid order side: ' + order_side)

    url = 'https://www.bithumb.com/trade/%s/%s' % (query_method, currency)

    params = {}
    params['coinAmt'] = price
    params['0'] = 'attempted_agreement'
    params['selectAsset'] = 'KRW'
    params['coinAmtComma'] = format(price, ',')
    params['coinQty'] = qty
    params['csrf_xcoin_name'] = self.session.cookies.get('csrf_xcoin_name')

    headers = {}
    headers['Referer'] = 'https://www.bithumb.com/trade/order/' + currency
    update = self._query(url, 'POST', params, headers=headers)
    self._assert_success(update)
    return update

  def query_account_balance(self):
    url = "https://www.bithumb.com/trade/getAsset/DASH"
    params = {
        'csrf_xcoin_name': self.session.cookies.get('csrf_xcoin_name'), '_': int(time.time() * 1e6)
    }

    headers = {}
    headers['Referer'] = 'https://www.bithumb.com/trade/order/XRP'
    update = self._query(url, 'GET', params, headers=headers)
    self._assert_success(update)
    assert update.msg['data']['KRW']['TOTAL'] != 0
    return update

  def query_submit_buy(self, currency, price, qty):
    resp = self._send_order(currency, price, qty, 'buy')
    return resp

  def query_submit_sell(self, currency, price, qty):
    resp = self._send_order(currency, price, qty, 'sell')
    return resp

  def query_list_orders(self, currency, interval_day=5000, order_type='standby'):
    assert currency in exchange_constants.coin_list, \
      'Not supported currency: ' + currency

    # TODO(wooyaggo): Need to verify that 1P is buy or 2P is sell.
    if order_type == 'standby':
      # Show working orders.
      status = '1P,2P'
    elif order_type == 'complete':
      # Show completed order.
      status = '1F,2F'
    else:
      raise Exception('Invalid order_type: ' + order_type)

    params = {
        'page': '1',  # Does not affect to the result.
        'tradeType': 'buy',  # Does not affect to the result.
        'status': status,
        'nIntervalDay': str(interval_day),
        'csrf_xcoin_name': self.session.cookies.get('csrf_xcoin_name')
    }

    url = 'https://www.bithumb.com/trade/getTransactionList/%s?_=%d' % (currency,
                                                                        int(time.time() * 1e6))

    headers = {}
    headers['Referer'] = 'https://www.bithumb.com/trade/order/' + currency
    update = self._query(url, 'POST', params, headers=headers)
    self._assert_success(update)
    return update

  def query_cancel_by_transaction_datetime(self, datetime_str, order_side, currency):
    # datetime_str example is '2018-02-21 22:11:13.951156'
    assert (type(datetime_str) == str
            and re.match('^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+){0,1}$', datetime_str))
    assert currency in exchange_constants.coin_list, \
      'Not supported currency: ' + currency

    if order_side == 'buy':
      query_method = 'cancelBuy'
    elif order_side == 'sell':
      query_method = 'cancelSell'
    else:
      raise Exception('Invalid order side: ' + order_side)

    url = 'https://www.bithumb.com/trade/%s/%s?_=%d' % (
        query_method, currency, int(time.time() * 1e6))

    params = {'reqDt': datetime_str, 'csrf_xcoin_name': self.session.cookies.get('csrf_xcoin_name')}

    headers = {}
    headers['Referer'] = 'https://www.bithumb.com/trade/order/' + currency
    update = self._query(url, 'POST', params, headers=headers)
    self._assert_success(update)
    return update

  def query_cancel_product(self, currency):
    assert currency in exchange_constants.coin_list, \
      'Not supported currency: ' + currency

    num_prev_working_order = None
    while True:
      orders = self.query_list_orders(currency).msg['data']

      num_curr_working_order = len(orders['list'])
      if num_curr_working_order == 0:
        break

      if (num_prev_working_order is not None and num_curr_working_order >= num_prev_working_order):
        raise Exception('Failed on cancel all orders')

      num_prev_working_order = num_curr_working_order
      for order in orders['list']:
        transaction_datetime = order['TRAN_DT']

        # HACK(wooyaggo): Get order type by misu field.
        if order['BUY_MISU_YN'] == '':
          order_side = 'sell'
        elif order['SELL_MISU_YN'] == '':
          order_side = 'buy'
        else:
          raise Exception('Can not decide order type: ' + repr(order))
        self.query_cancel_by_transaction_datetime(transaction_datetime, order_side, currency)

  def query_coupon_list(self, page=1):
    url = 'https://www.bithumb.com/json/getMemCouponList?_=%d' % (int(time.time() * 1e6))

    params = {'page': page, 'csrf_xcoin_name': self.session.cookies.get('csrf_xcoin_name')}

    headers = {}
    headers['Referer'] = 'https://www.bithumb.com/u5/US505'
    update = self._query(url, 'POST', params, headers=headers)
    assert update.msg['error'] == 0
    return update
